#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
SIMION自动化运行器 - Python后端服务
适配Electron+Vue前端
"""
import platform
import csv
import os
import sys
import subprocess
import re
import time
import signal
import threading
import math
import numpy as np
import json
import glob
import shutil
from concurrent.futures import ThreadPoolExecutor, as_completed
from i18n import set_language, get_language, t
from pathlib import Path
import logging
import stat
from utils.logging import get_logger, get_log_queue, setup_console_logger
from utils.common import setup_console_encoding
from scipy.spatial import ConvexHull
try:
    from simion_workspace import SimionWorkspaceManager
    WORKSPACE_AVAILABLE = True
except ImportError:
    WORKSPACE_AVAILABLE = False


setup_console_encoding()
logger = setup_console_logger(__name__)
log_queue = get_log_queue()

class SimionRunner:
    """SIMION自动化运行器"""
    
    def __init__(self):
        self.is_running = False
        self.stop_requested = False
        self.current_process = None
        self.workspace_manager = None
        self._io_lock = threading.Lock()
        
    def run_simion_fly(self, simion_path, iob_path, lua_path, frequency, thread_id=None, work_dir=None):
        """执行SIMION fly命令 simion.exe --nogui --quiet fly iob_path"""
        try:
            # 先更新Lua文件中的频率参数
            if not self.update_lua_frequency(lua_path, frequency, thread_id):
                return False, "Lua文件更新失败", []
            
            cmd = [simion_path, '--nogui', '--quiet', 'fly', iob_path]
            
            cwd = work_dir if work_dir else os.path.dirname(iob_path)            

            if not os.path.exists(simion_path):
                logger.error(t('simion_not_found', path=simion_path))
                return False, t('simion_not_found', path=simion_path), []
            if not os.path.exists(iob_path):
                logger.error(t('iob_not_found', path=iob_path))
                return False, t('iob_not_found', path=iob_path), []
            if not os.path.exists(lua_path):
                logger.error(t('lua_not_found', path=lua_path))
                return False, t('lua_not_found', path=lua_path), []
        
            timeout_seconds = None
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                encoding='gbk',
                errors='replace',
                cwd=cwd
            )
            
            # 合并输出
            try:
                output_text = result.stdout + result.stderr
            except UnicodeDecodeError:
                output_text = (result.stdout.encode('utf-8', errors='replace').decode('utf-8') + 
                              result.stderr.encode('utf-8', errors='replace').decode('utf-8'))
            
            if result.returncode == 0:
                splats = self.parse_simion_output(output_text)
                return True, output_text, splats
            else:
                logger.error(t('command_execution_failed'))
                logger.error(t('command_details', cmd=cmd, code=result.returncode, work_dir=work_dir or t('current_directory')))
                
                # 分析返回码含义
                if result.returncode == 1:
                    logger.error(t('return_code_1'))
                elif result.returncode == 2:
                    logger.error(t('return_code_2'))
                elif result.returncode == 126:
                    logger.error(t('return_code_126'))
                elif result.returncode == 127:
                    logger.error(t('return_code_127'))
                elif result.returncode == 128:
                    logger.error(t('return_code_128'))
                elif result.returncode > 128:
                    signal_num = result.returncode - 128
                    logger.error(t('return_code_signal', code=result.returncode, signal=signal_num))
                    if signal_num == 9:
                        logger.error(t('signal_9'))
                    elif signal_num == 15:
                        logger.error(t('signal_15'))
                else:
                    logger.error(t('return_code_unknown', code=result.returncode))
                
                # 错误输出
                if result.stderr:
                    logger.error(t('error_output_details'))
                    stderr_lines = result.stderr.split('\n')
                    for i, line in enumerate(stderr_lines[:20]):  # 显示前20行
                        if line.strip():
                            logger.error(t('error_line', line_num=i+1, line=line))
                    if len(stderr_lines) > 20:
                        logger.error(t('error_more_lines', count=len(stderr_lines)-20))
                else:
                    logger.error(t('no_error_output'))
                
                # 标准输出
                if result.stdout:
                    logger.error(t('stdout_details'))
                    stdout_lines = result.stdout.split('\n')
                    for i, line in enumerate(stdout_lines[:10]):  # 显示前10行
                        if line.strip():
                            logger.error(t('output_line', line_num=i+1, line=line))
                    if len(stdout_lines) > 10:
                        logger.error(t('output_more_lines', count=len(stdout_lines)-10))
                
                return False, output_text, []
                
        except Exception as e:
            logger.error(t('exception_details', type=type(e).__name__, message=str(e), cmd=cmd, work_dir=work_dir or t('current_directory')))
            return False, str(e), []
    
    def update_lua_frequency(self, lua_path, frequency, thread_id=None):
        """更新Lua文件中的频率参数"""
        try:
            # 添加重试机制，处理文件访问竞争
            max_retries = 3
            for attempt in range(max_retries):
                if not os.path.exists(lua_path):
                    if attempt < max_retries - 1:
                        logger.warning(f"Lua文件不存在，重试 {attempt + 1}/{max_retries}: {lua_path}")
                        time.sleep(0.1)  
                        continue
                    else:
                        logger.error(f"Lua文件不存在: {lua_path}")
                        return False
                else:
                    break
            
            with self._io_lock:
                with open(lua_path, 'r', encoding='utf-8') as f:
                    content = f.read()
            
            # 替换频率值
            patterns = [
                r'(adjustable\s+_frequency\s*=\s*)\d+\.?\d*',
                r'(adjustable\s+_frequency\s*=\s*)\d+',
                r'(_frequency\s*=\s*)\d+\.?\d*',
            ]
            
            new_content = content
            frequency_updated = False
    
            for pattern in patterns:
                updated = re.sub(pattern, f'\\g<1>{int(frequency)}', new_content)
                if updated != new_content:
                    new_content = updated
                    frequency_updated = True
                    break
            
            # 检查是否找到频率参数
            if not frequency_updated:
                if f'_frequency = {int(frequency)}' in content:
                    frequency_updated = True
                else:
                    return False
            
            # 如果频率已经是目标值，直接返回成功
            if not frequency_updated:
                return True
            
            # 写回文件，添加重试机制，使用线程锁保护
            for write_attempt in range(max_retries):
                try:
                    with self._io_lock:  
                        with open(lua_path, 'w', encoding='utf-8') as f:
                            f.write(new_content)
                            f.flush()
                            os.fsync(f.fileno())  
                    return True
                except Exception as write_error:
                    if write_attempt < max_retries - 1:
                        logger.warning(f"写入Lua文件失败，重试 {write_attempt + 1}/{max_retries}: {write_error}")
                        time.sleep(0.1)
                        continue
                    else:
                        logger.error(f"写入Lua文件最终失败: {write_error}")
                        raise
            
        except Exception as e:
            logger.error(f"更新Lua文件失败: {e}")
            return False
    
    def parse_simion_output(self, output_text):
        """解析SIMION输出，提取(x, y, z, mass, vx, vy, vz, ke, t, charge)元组的列表"""
        splats = []
        splat_pattern = r'^Splat at x=([\d.-]+) mm, y=([\d.-]+) mm, z=([\d.-]+) mm, mass=([\d.-]+) amu, vx=([\d.-]+) mm/us, vy=([\d.-]+) mm/us, vz=([\d.-]+) mm/us, KE=([\d.-]+) eV, t=([\d.-]+) us, charge=([\d.-]+)(?:, f=[\d.]+)?$'
        lines = [ln.strip() for ln in output_text.split('\n') if ln.strip()]
        splat_lines = 0
        for i, line in enumerate(lines):
            if line.startswith('Splat at'):
                splat_lines += 1
                
                match = re.search(splat_pattern, line)
                if match:
                    try:
                        x = float(match.group(1))
                        y = float(match.group(2))
                        z = float(match.group(3))
                        mass = float(match.group(4))
                        vx = float(match.group(5))
                        vy = float(match.group(6))
                        vz = float(match.group(7))
                        ke = float(match.group(8))
                        t = float(match.group(9))
                        charge = float(match.group(10))
                        splats.append((x, y, z, mass, vx, vy, vz, ke, t, charge))
                    except ValueError as e:
                        logger.warning(f"解析Splat数据时数值转换错误: {e}, 行: {line[:100]}...")
                        continue
                else:
                    if splat_lines <= 3:
                        logger.warning(f"=== 正则表达式匹配失败 ===")
                        logger.warning(f"Splat行 {splat_lines}: {line} 正则表达式: {splat_pattern}")
                        
                        simple_pattern = r'Splat at x=([\d.-]+)'
                        simple_match = re.search(simple_pattern, line)
                        if simple_match:
                            logger.warning(f"简单匹配成功，x值: {simple_match.group(1)}")
                        else:
                            logger.warning("连简单匹配都失败了")
        
        return splats
    
    def calculate_emittance(self, splats):
        """计算发射度（RMS发射度、几何发射度、规范化发射度）- 使用每个粒子的vz"""
        if len(splats) < 2:
            return {
                'rms_x': 0.0, 'rms_y': 0.0,
                'geometric_x': 0.0, 'geometric_y': 0.0,
                'normalized_x': 0.0, 'normalized_y': 0.0
            }
        
        x_data = [splat[0] for splat in splats]
        y_data = [splat[1] for splat in splats]
        vx_data = [splat[4] for splat in splats]
        vy_data = [splat[5] for splat in splats]
        vz_data = [splat[6] for splat in splats]
        
        # 使用每个粒子的实际vz计算x'和y'
        x_prime = []
        y_prime = []
        for i in range(len(vx_data)):
            vz = vz_data[i] if abs(vz_data[i]) > 1e-10 else 1e-10
            x_prime.append(vx_data[i] / vz)
            y_prime.append(vy_data[i] / vz)
        
        # 使用总体矩计算RMS发射度（与Twiss.py一致）
        def calculate_rms_emittance(pos_data, angle_data):
            if len(pos_data) < 2:
                return 0.0
            
            # 中心化数据
            pos_centered = np.array(pos_data) - np.mean(pos_data)
            angle_centered = np.array(angle_data) - np.mean(angle_data)
            
            # 使用总体矩（除以N）- 与Twiss.py一致
            pos_var = np.mean(pos_centered**2)                    # ⟨x²⟩
            angle_var = np.mean(angle_centered**2)                # ⟨x'²⟩
            pos_angle_cov = np.mean(pos_centered * angle_centered)  # ⟨xx'⟩
            
            rms_emittance = math.sqrt(max(0, pos_var * angle_var - pos_angle_cov**2))
            return rms_emittance
        
        # 计算x和y方向的RMS发射度
        rms_x = calculate_rms_emittance(x_data, x_prime)
        rms_y = calculate_rms_emittance(y_data, y_prime)
        
        # 几何发射度 = 4 * RMS发射度
        geometric_x = 4 * rms_x
        geometric_y = 4 * rms_y
        
        # 规范化发射度计算（保持不变）
        ke_data = [splat[7] for splat in splats]
        ke_avg = np.mean(ke_data) if ke_data else 1000.0
        
        v_avg = math.sqrt(np.mean([vx**2 + vy**2 + vz**2 for vx, vy, vz in zip(vx_data, vy_data, vz_data)]))
        c = 299792458
        beta = min(v_avg * 1e6 / c, 0.99)
        gamma = 1.0 / math.sqrt(1 - beta**2) if beta < 0.99 else 10.0
        
        normalized_x = beta * gamma * geometric_x
        normalized_y = beta * gamma * geometric_y
        
        return {
            'rms_x': rms_x,
            'rms_y': rms_y,
            'geometric_x': geometric_x,
            'geometric_y': geometric_y,
            'normalized_x': normalized_x,
            'normalized_y': normalized_y
        }   
    
    def calculate_convex_hull_area(self, x_data, y_data):
        """计算凸包面积 - 与MATLAB的convhull+polyarea完全对应"""
        if len(x_data) < 3:
            return 0.0
        if len(set(x_data)) == 1:  # 所有x坐标相同
            logger.warning(f"数据退化：所有x坐标相同 ({x_data[0]})，无法计算凸包")
            return 0.0
        if len(set(y_data)) == 1:  # 所有y坐标相同
            logger.warning(f"数据退化：所有y坐标相同 ({y_data[0]})，无法计算凸包")
            return 0.0
        if len(set(zip(x_data, y_data))) == 1:
            logger.warning("数据退化：所有点重合，无法计算凸包")
            return 0.0
        
        try:
            # k = convhull(x, y)
            points = np.column_stack((x_data, y_data))
            hull = ConvexHull(points)
            hull_vertices = hull.vertices
            # polyarea(x(k), y(k))
            x_hull = [x_data[int(i)] for i in hull_vertices]
            y_hull = [y_data[int(i)] for i in hull_vertices]
            return self._polyarea(x_hull, y_hull)
        except Exception as e:
            error_msg = str(e)
            if "qhull input error" in error_msg or "less than 2-dimensional" in error_msg:
                logger.warning(f"QHull退化数据错误，跳过凸包计算: {error_msg}")
                return 0.0
            else:
                logger.warning(f"凸包计算失败，使用简化版本: {e}")
                return 0.0
    
    def _polyarea(self, x, y):
        """计算多边形面积 - 完全对应MATLAB的polyarea函数"""
        if len(x) < 3:
            return 0.0
        # Shoelace公式：A = 0.5 * |sum(x[i]*y[i+1] - x[i+1]*y[i])|
        n = len(x)
        area = 0.0
        for i in range(n):
            j = (i + 1) % n
            area += x[i] * y[j] - x[j] * y[i]
        return abs(area) / 2.0
    
    def process_single_frequency(self, args):
        """处理单个频率的线程函数"""
        simion_path, iob_path, lua_path, csv_save_path, frequency, index, total = args
        
        thread_id = threading.current_thread().name
        
        try:
            # 获取工作目录和Lua文件路径
            work_dir = None
            thread_lua_path = lua_path  
            if hasattr(self, 'workspace_manager') and self.workspace_manager:
                # 循环复用
                thread_count = self.workspace_manager.thread_count
                run_id = ((index - 1) % thread_count) + 1
                workspace_info = self.workspace_manager.get_thread_workspace(run_id)
                work_dir = workspace_info['work_dir']
                thread_lua_path = os.path.join(workspace_info['work_dir'], os.path.basename(lua_path))
            
            success, output_text, splats = self.run_simion_fly(
                simion_path, iob_path, thread_lua_path, frequency, thread_id, work_dir
            )
            
            if success:
                if splats:
                    # 计算发射度
                    emittance_data = self.calculate_emittance(splats)
                    # 计算凸包面积
                    x_data = [splat[0] for splat in splats]
                    y_data = [splat[1] for splat in splats]
                    convex_hull_area = self.calculate_convex_hull_area(x_data, y_data) 
                    # 保存CSV文件
                    csv_path = self.save_csv_with_data(
                        splats, frequency, csv_save_path, emittance_data, convex_hull_area
                    )
                    if csv_path:
                        # 获取详细的计算结果
                        rms_x = emittance_data.get('rms_x', 0.0)
                        rms_y = emittance_data.get('rms_y', 0.0)
                        geometric_x = emittance_data.get('geometric_x', 0.0)
                        geometric_y = emittance_data.get('geometric_y', 0.0)
                        normalized_x = emittance_data.get('normalized_x', 0.0)
                        normalized_y = emittance_data.get('normalized_y', 0.0)
                        
                        # 计算总发射度（几何平均值）
                        rms_total = math.sqrt(rms_x * rms_y) if rms_x > 0 and rms_y > 0 else max(rms_x, rms_y)
                        geometric_total = math.sqrt(geometric_x * geometric_y) if geometric_x > 0 and geometric_y > 0 else max(geometric_x, geometric_y)
                        normalized_total = math.sqrt(normalized_x * normalized_y) if normalized_x > 0 and normalized_y > 0 else max(normalized_x, normalized_y)
                        
                        # 计算进度百分比
                        progress_percent = (index / total) * 100
                        progress_bar = self.create_progress_bar(index, total)
                        
                        logger.success(t('frequency_completed', 
                                       frequency=frequency, 
                                       len_splats=len(splats), 
                                       index=index, 
                                       total=total, 
                                       progress_bar=progress_bar, 
                                       progress_percent=progress_percent, 
                                       convex_hull_area=convex_hull_area))
                        return True, frequency, t('frequency_saved', frequency=frequency, filename=f"frequency_{frequency}_Hz.csv")
                    else:
                        return False, frequency, t('file_error', error="保存CSV文件失败")
                else:
                    logger.warning(f"频率 {frequency} Hz: SIMION命令执行成功，但未解析到有效的Splat数据")
                    logger.warning(f"  这可能是因为：1. 粒子轨迹没有到达检测器 2. 检测器位置设置不当 3. 粒子能量不足或轨迹偏离 4. Lua脚本中的检测逻辑问题")
                    return False, frequency, t('frequency_failed', frequency=frequency, error="未检测到有效粒子数据")
            else:
                # SIMION命令执行失败
                return False, frequency, t('frequency_failed', frequency=frequency, error="SIMION命令执行失败") 
        except Exception as e:
            logger.error(f"处理频率 {frequency} Hz 时出错: {e}")
            return False, frequency, t('frequency_failed', frequency=frequency, error=str(e))
    
    def save_csv_with_data(self, splats, frequency, csv_save_path, emittance_data, convex_hull_area):
        """保存包含发射度数据的CSV文件和summary.csv"""
        try:
            with self._io_lock:  # 使用线程锁保护目录创建
                os.makedirs(csv_save_path, exist_ok=True)

            if not os.path.exists(csv_save_path):
                logger.error(f"无法创建输出目录: {csv_save_path}")
                return None
            if not os.access(csv_save_path, os.W_OK):
                logger.error(f"输出目录不可写: {csv_save_path}")
                return None
            
            # 计算总发射度（几何平均值）
            rms_x = emittance_data.get('rms_x', 0)
            rms_y = emittance_data.get('rms_y', 0)
            geometric_x = emittance_data.get('geometric_x', 0)
            geometric_y = emittance_data.get('geometric_y', 0)
            normalized_x = emittance_data.get('normalized_x', 0)
            normalized_y = emittance_data.get('normalized_y', 0)
            
            rms_total = math.sqrt(rms_x * rms_y) if rms_x > 0 and rms_y > 0 else max(rms_x, rms_y)
            geometric_total = math.sqrt(geometric_x * geometric_y) if geometric_x > 0 and geometric_y > 0 else max(geometric_x, geometric_y)
            normalized_total = math.sqrt(normalized_x * normalized_y) if normalized_x > 0 and normalized_y > 0 else max(normalized_x, normalized_y)
            
            # 生成简化的文件名（避免文件名过长）
            filename = f"freq_{frequency:.0f}Hz_rms_{rms_total:.2e}_geom_{geometric_total:.2e}_norm_{normalized_total:.2e}_hull_{convex_hull_area:.2e}.csv"
            # 使用os.path.join确保正确的路径分隔符
            target_path = os.path.join(csv_save_path, filename)
            # 规范化路径，确保使用正确的分隔符
            target_path = os.path.normpath(target_path)
            # 准备数据
            headers = ['x', 'y', 'z', 'mass', 'vx', 'vy', 'vz', 'ke', 't', 'charge', 'f']    
            data = []
            for splat in splats:
                if len(splat) >= 10:
                    x, y, z, mass, vx, vy, vz, ke, t, charge = splat[:10]
                    data.append([x, y, z, mass, vx, vy, vz, ke, t, charge, frequency])
            
            # 写入CSV文件，使用线程锁保护
            with self._io_lock:  # 使用线程锁保护CSV文件写入
                with open(target_path, 'w', newline='', encoding='utf-8') as file:
                    csv_writer = csv.writer(file, delimiter=',')
                    csv_writer.writerow(headers)
                    for row in data:
                        csv_writer.writerow(row)
            
            # 保存summary.csv
            self.save_summary_csv(csv_save_path, frequency, convex_hull_area, emittance_data, splats)
            return target_path
        except Exception as e:
            logger.error(f"保存CSV文件失败: {e} 目标路径: {target_path} 输出目录: {csv_save_path} 目录是否存在: {os.path.exists(csv_save_path)} 目录是否可写: {os.access(csv_save_path, os.W_OK) if os.path.exists(csv_save_path) else 'N/A'}")
            return None
    
    def save_summary_csv(self, csv_save_path, frequency, convex_hull_area, emittance_data, splats=None):
        """保存summary.csv文件，包含频率、凸包面积、各种发射度数据和原子质量"""
        try:
            summary_path = os.path.join(csv_save_path, 'summary.csv')
            file_exists = os.path.exists(summary_path)
            mass_value = 0.0 # 获取Splat中的mass值
            if splats and len(splats) > 0:
                if len(splats[0]) > 3:
                    mass_value = splats[0][3]  
                else:
                    logger.warning("Splat数据格式不正确，质量设为0")
            else:
                logger.warning("未提供splat数据，质量设为0")
            
            # 计算总发射度（几何平均值）
            rms_x = emittance_data.get('rms_x', 0)
            rms_y = emittance_data.get('rms_y', 0)
            geometric_x = emittance_data.get('geometric_x', 0)
            geometric_y = emittance_data.get('geometric_y', 0)
            normalized_x = emittance_data.get('normalized_x', 0)
            normalized_y = emittance_data.get('normalized_y', 0)
            
            rms_total = math.sqrt(rms_x * rms_y) if rms_x > 0 and rms_y > 0 else max(rms_x, rms_y)
            geometric_total = math.sqrt(geometric_x * geometric_y) if geometric_x > 0 and geometric_y > 0 else max(geometric_x, geometric_y)
            normalized_total = math.sqrt(normalized_x * normalized_y) if normalized_x > 0 and normalized_y > 0 else max(normalized_x, normalized_y)
            
            # 准备数据行，保留6位小数
            try:
                row_data = [
                    frequency,
                    f"{mass_value:.6f}",  
                    f"{convex_hull_area:.6f}",
                    f"{rms_x:.6f}",
                    f"{rms_y:.6f}",
                    f"{rms_total:.6f}",
                    f"{geometric_x:.6f}",
                    f"{geometric_y:.6f}",
                    f"{geometric_total:.6f}",
                    f"{normalized_x:.6f}",
                    f"{normalized_y:.6f}",
                    f"{normalized_total:.6f}"
                ]
            except Exception as data_error:
                logger.error(f"准备summary数据失败: {data_error}")
                raise
            
            with self._io_lock:  # 使用线程锁保护summary.csv写入
                with open(summary_path, 'a', newline='', encoding='utf-8') as file:
                    csv_writer = csv.writer(file, delimiter=',')
                    
                    # 如果是新文件，写入表头
                    if not file_exists:
                        headers = [
                            'frequency',
                            'mass', 
                            'convex_hull_area',
                            'rms_x',
                            'rms_y', 
                            'rms_total',
                            'geometric_x',
                            'geometric_y',
                            'geometric_total',
                            'normalized_x',
                            'normalized_y',
                            'normalized_total'
                        ]
                        csv_writer.writerow(headers)
                    
                    # 写入数据行
                    csv_writer.writerow(row_data)
            
        except Exception as e:
            logger.error(f"保存summary.csv失败: {e} 目标路径: {summary_path} 输出目录: {csv_save_path} 目录是否存在: {os.path.exists(csv_save_path)} 目录是否可写: {os.access(csv_save_path, os.W_OK) if os.path.exists(csv_save_path) else 'N/A'}")
    
    def create_progress_bar(self, current, total, width=20):
        """创建文本进度条"""
        if total == 0:
            return "█" * width
        
        filled = int((current / total) * width)
        bar = "█" * filled + "░" * (width - filled)
        return bar
    
    def run_automation(self, config):
        """运行SIMION自动化"""
        try:
            start_time = time.time()  
            language = config.get('language', 'zh-CN')
            set_language(language)
            self.is_running = True
            self.stop_requested = False
            logger.info("=== " + t('automation_started') + " ===")
            simion_path = config['simion_path']
            iob_path = config['iob_path']
            lua_path = config['lua_path']
            csv_save_path = config['csv_save_path']
            frequency_list = config['frequency_list']
            thread_count = config.get('thread_count', 2)
            iob_dir = Path(iob_path).parent
            workspace_dir = iob_dir / "simion_workspace"
            
            if workspace_dir.exists():
                logger.warning("检测到simion_workspace目录已存在")
                logger.warning(f"工作空间路径: {workspace_dir}")
                logger.warning("这可能是上次运行遗留的工作空间，建议手动清理")
                logger.warning("如果继续运行，系统将自动清理旧的工作空间")
                
                try:
                    workspace_contents = list(workspace_dir.iterdir())
                    logger.warning(f"工作空间包含 {len(workspace_contents)} 个项目:")
                    for item in workspace_contents[:5]:  # 只显示前5个
                        logger.warning(f"  - {item.name}")
                    if len(workspace_contents) > 5:
                        logger.warning(f"  ... 还有{len(workspace_contents)-5}个项目")
                except Exception as e:
                    logger.warning(f"无法读取工作空间内容: {e}")
            else:
                logger.info("未检测到simion_workspace目录，可以正常启动")
            
            required_params = ['simion_path', 'iob_path', 'lua_path', 'csv_save_path', 'frequency_list']
            for param in required_params:
                if not config.get(param):
                    raise ValueError(f"缺少必要参数: {param}")
            
            logger.info(f"频率列表长度: {len(frequency_list)}")
            logger.info(f"线程数量: {thread_count}")
            
            with self._io_lock:  
                os.makedirs(csv_save_path, exist_ok=True)
            
            # 优化：对于单频率任务，禁用工作空间管理以提高性能
            logger.info(f"=== 工作空间管理决策 ===")
            logger.info(f"线程数量: {thread_count} 频率数量: {len(frequency_list)}")
            
            if WORKSPACE_AVAILABLE and thread_count > 1 and len(frequency_list) > 1:
                actual_thread_count = min(thread_count, len(frequency_list))
                self.workspace_manager = SimionWorkspaceManager(iob_path, actual_thread_count, lua_path)
                
                if not self.workspace_manager.setup_workspace():
                    logger.error("工作空间设置失败，回退到单线程模式")
                    thread_count = 1
                    self.workspace_manager = None
                else:
                    logger.info("✅ 多任务模式：使用并行执行，工作空间已隔离")
            else:
                # 单频率任务或工作空间不可用时，直接使用原始文件
                self.workspace_manager = None
                if len(frequency_list) == 1:
                    logger.info("单频率任务，禁用工作空间管理以提高性能")
                    thread_count = 1  # 单频率任务使用单线程
                elif thread_count > 1:
                    logger.warning("工作空间管理不可用，使用单线程模式")
                    thread_count = 1
                else:
                    logger.info("使用单线程模式")
            
            # 准备线程参数
            total_frequencies = len(frequency_list)
            thread_args = []
        
            for i, freq in enumerate(frequency_list, 1):
                if self.workspace_manager:
                    thread_count = self.workspace_manager.thread_count
                    run_id = ((i - 1) % thread_count) + 1
                    workspace_info = self.workspace_manager.get_thread_workspace(run_id)
                    thread_iob_path = workspace_info['iob_path']
                    thread_lua_path = os.path.join(workspace_info['work_dir'], os.path.basename(lua_path))
                    thread_output_dir = csv_save_path # CSV文件始终保存到项目目录
                else:
                    thread_iob_path = iob_path
                    thread_lua_path = lua_path
                    thread_output_dir = csv_save_path
                
                thread_args.append((
                    simion_path, thread_iob_path, thread_lua_path, 
                    thread_output_dir, freq, i, total_frequencies
                ))
            
            # 使用线程池处理
            success_count = 0
            failed_count = 0
            error_messages = []  # 收集错误信息
            start_time = time.time()
            
            # 动态计算线程池大小
            task_count = len(thread_args)
            cpu_count = os.cpu_count() or 1
            
            # 动态计算线程池大小
            if task_count <= cpu_count:
                # 任务数小于等于CPU核心数，使用任务数作为线程数
                actual_thread_count = task_count
                logger.info(f"任务数({task_count}) ≤ CPU核心数({cpu_count})，使用任务数作为线程数")
            else:
                # 任务数大于CPU核心数，使用CPU核心数作为线程数
                actual_thread_count = cpu_count
                logger.info(f"任务数({task_count}) > CPU核心数({cpu_count})，使用CPU核心数作为线程数")
            
            # 如果使用了工作空间管理，确保线程数不超过工作空间线程数
            if self.workspace_manager:
                workspace_thread_count = self.workspace_manager.thread_count
                if actual_thread_count > workspace_thread_count:
                    logger.info(f"调整线程数从{actual_thread_count}到{workspace_thread_count}以匹配工作空间")
                    actual_thread_count = workspace_thread_count
            
            logger.info(f"=== 线程池执行 ===")
            logger.info(f"启动线程池: {actual_thread_count} 个线程")
            logger.info(f"提交任务数: {task_count}")
            logger.info(f"CPU核心数: {cpu_count}")
            
            with ThreadPoolExecutor(max_workers=actual_thread_count) as executor:
                # 提交所有任务
                future_to_freq = {
                    executor.submit(self.process_single_frequency, args): args[4] 
                    for args in thread_args
                }
                
                logger.info(f"已提交 {len(future_to_freq)} 个任务到线程池")
                
                # 处理完成的任务
                for future in as_completed(future_to_freq):
                    if self.stop_requested:
                        # 取消未完成的任务
                        for f in future_to_freq:
                            f.cancel()
                        break
                    
                    frequency = future_to_freq[future]
                    try:
                        success, freq, message = future.result()
                        if success:
                            success_count += 1
                        else:
                            failed_count += 1
                            error_messages.append(f"频率 {freq} Hz: {message}")
                    except Exception as e:
                        failed_count += 1
                        error_msg = f"频率 {frequency} Hz: 处理异常 - {str(e)}"
                        error_messages.append(error_msg)
                        logger.error(f"处理频率 {frequency} 时出错: {e}")
            
            # 清理临时文件
            self._cleanup_temp_files(csv_save_path)
            
            # 清理工作空间（如果使用了工作空间管理）
            if self.workspace_manager:
                logger.info("清理工作空间...")
                self.workspace_manager.cleanup_workspace(keep_workspace=False)
            else:
                logger.info("未使用工作空间管理，跳过工作空间清理")
            
            # 强制清理可能遗留的工作空间目录
            self._force_cleanup_workspace_if_exists(iob_path)
            
            # 输出统计信息
            logger.info("=== SIMION自动化完成 ===")
            logger.success(f"成功处理: {success_count} 个频率")
            logger.error(f"失败处理: {failed_count} 个频率")
            logger.info(f"总计: {success_count + failed_count} 个频率")
            
            # 计算运行时间
            end_time = time.time()
            total_time = end_time - start_time
            
            # 输出错误信息
            if error_messages:
                logger.error("=== 错误详情 ===")
                for error_msg in error_messages:
                    logger.error(error_msg)
            
            return {
                'success': True,
                'success_count': success_count,
                'failed_count': failed_count,
                'total_count': success_count + failed_count,
                'error_messages': error_messages,
                'runtime_seconds': round(total_time, 2)
            }
            
        except Exception as e:
            logger.error(f"自动化过程中发生错误: {e}")
            return {
                'success': False,
                'error': str(e)
            }
        finally:
            self.is_running = False
            # 清理工作空间（在主线程中进行，确保日志能被正确捕获）
            if hasattr(self, 'workspace_manager') and self.workspace_manager:
                try:
                    self.workspace_manager.cleanup_workspace(keep_workspace=False)
                    logger.info("工作空间清理完成")
                except Exception as e:
                    logger.error(f"清理工作空间失败: {e}")
    
    def stop_automation(self):
        """停止自动化"""
        self.stop_requested = True
        if self.current_process:
            try:
                self.current_process.terminate()
            except:
                pass
        logger.info("收到停止信号，正在安全退出...")
        
        # 强制清理所有工作空间
        self._force_cleanup_all_workspaces()
    
    def _force_cleanup_all_workspaces(self):
        """强制清理所有工作空间目录"""
        try:
            logger.info("=== 强制清理所有工作空间 ===")
            
            # 清理当前工作空间管理器
            if hasattr(self, 'workspace_manager') and self.workspace_manager:
                try:
                    logger.info("清理当前工作空间管理器...")
                    self.workspace_manager.cleanup_workspace(keep_workspace=False, force=True)
                    logger.info("当前工作空间管理器清理完成")
                except Exception as e:
                    logger.error(f"清理当前工作空间管理器失败: {e}")
            
            # 扫描并清理所有遗留的simion_workspace目录
            self._scan_and_cleanup_workspace_directories()
            
            logger.info("所有工作空间强制清理完成")
            
        except Exception as e:
            logger.error(f"强制清理工作空间失败: {e}")
    
    def _scan_and_cleanup_workspace_directories(self):
        """扫描并清理所有遗留的工作空间目录"""
        try:       
            # 扫描所有可能的simion_workspace目录
            workspace_patterns = [
                "**/simion_workspace",
                "**/simion_workspace_*",
                "**/temp_*",
                "**/run*"
            ]
            
            cleaned_dirs = []
            total_size = 0
            
            for pattern in workspace_patterns:
                for workspace_dir in glob.glob(pattern, recursive=True):
                    if os.path.isdir(workspace_dir):
                        try:
                            # 计算目录大小
                            dir_size = self._get_directory_size(workspace_dir)
                            total_size += dir_size
                            
                            # 强制删除目录
                            self._force_delete_directory(workspace_dir)
                            cleaned_dirs.append(workspace_dir)
                            
                            logger.info(f"已清理工作空间目录: {workspace_dir} ({self._format_size(dir_size)})")
                            
                        except Exception as e:
                            logger.warning(f"清理目录失败 {workspace_dir}: {e}")
            
            if cleaned_dirs:
                logger.info(f"共清理了 {len(cleaned_dirs)} 个工作空间目录")
                logger.info(f"释放磁盘空间: {self._format_size(total_size)}")
            else:
                logger.info("未发现遗留的工作空间目录")
                
        except Exception as e:
            logger.error(f"扫描清理工作空间目录失败: {e}")
    
    def get_status(self):
        """获取运行状态"""
        return {
            'is_running': self.is_running,
            'stop_requested': self.stop_requested
        }
    
    def _cleanup_temp_files(self, project_path):
        """清理项目目录中的临时文件"""
        try:
            project_dir = Path(project_path).parent
            temp_patterns = [
                '*.tmp',
                '*.wrk', 
                '*.log',
                '*.out',
                '*.err',
                '*.cache',
                '*.lock',
                'temp_*',
                'simion_*',
                'fly_*'
            ]
            
            cleaned_count = 0
            for pattern in temp_patterns:
                temp_files = list(project_dir.glob(pattern))
                for temp_file in temp_files:
                    try:
                        if temp_file.is_file():
                            temp_file.unlink()
                            cleaned_count += 1
                    except Exception as e:
                        logger.warning(f"删除临时文件失败 {temp_file.name}: {e}")
            
            if cleaned_count > 0:
                logger.info(f"清理了 {cleaned_count} 个临时文件")
            else:
                logger.info("没有找到需要清理的临时文件")
                
        except Exception as e:
            logger.error(f"清理临时文件失败: {e}")
    
    def _force_cleanup_workspace_if_exists(self, iob_path):
        """强制清理可能遗留的工作空间目录"""
        try:
            from pathlib import Path
            base_dir = Path(iob_path).parent
            workspace_dir = base_dir / "simion_workspace"
            
            if workspace_dir.exists():
                logger.info(f"发现遗留的工作空间目录: {workspace_dir}")
                logger.info("开始强制清理...")
                
                # 计算目录大小
                total_size = self._get_directory_size(workspace_dir)
                size_str = self._format_size(total_size)
                logger.info(f"工作空间目录大小: {size_str}")
                
                # 尝试多种删除方法
                success = self._force_delete_directory(workspace_dir)
                
                if success:
                    logger.info(f"✅ 工作空间目录已成功删除，释放磁盘空间: {size_str}")
                else:
                    logger.warning(f"⚠️ 工作空间目录删除失败，可能被其他进程占用")
                    logger.warning("建议手动删除该目录以释放磁盘空间")
                    
                    # 启动后台清理任务
                    self._start_background_cleanup(workspace_dir)
            else:
                logger.info("未发现遗留的工作空间目录")
                
        except Exception as e:
            logger.error(f"强制清理工作空间失败: {e}")
    
    def _force_delete_directory(self, directory):
        """强制删除目录，使用多种方法"""     
        try:
            # 方法1: 标准删除
            logger.info("尝试标准删除...")
            shutil.rmtree(directory, ignore_errors=True)
            if not directory.exists():
                logger.info("标准删除成功")
                return True
            
            # 方法2: 修改权限后删除
            logger.info("尝试修改权限后删除...")
            self._make_directory_writable(directory)
            shutil.rmtree(directory, ignore_errors=True)
            if not directory.exists():
                logger.info("权限修改后删除成功")
                return True
            
            # 方法3: 等待后重试
            logger.info("等待3秒后重试删除...")
            time.sleep(3)
            shutil.rmtree(directory, ignore_errors=True)
            if not directory.exists():
                logger.info("等待后删除成功")
                return True
            
            # 方法4: 使用Windows命令强制删除
            if platform.system() == "Windows":
                logger.info("尝试使用Windows命令强制删除...")
                try:
                    # 使用rmdir /s /q命令强制删除
                    result = subprocess.run(
                        ["cmd", "/c", "rmdir", "/s", "/q", str(directory)],
                        capture_output=True,
                        text=True,
                        timeout=30
                    )
                    if not directory.exists():
                        logger.info("Windows命令删除成功")
                        return True
                    else:
                        logger.warning(f"Windows命令删除失败: {result.stderr}")
                except Exception as cmd_e:
                    logger.warning(f"Windows命令删除异常: {cmd_e}")
            
            # 方法5: 重命名目录（作为最后手段）
            logger.info("尝试重命名目录...")
            import datetime
            timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
            old_workspace = directory.parent / f"simion_workspace_old_{timestamp}"
            
            # 尝试多次重命名
            for attempt in range(3):
                try:
                    directory.rename(old_workspace)
                    logger.info(f"✅ 已重命名工作空间为: {old_workspace}")
                    logger.info("重命名成功，原目录已移除，新目录将在后台清理")
                    return True
                except Exception as rename_e:
                    logger.warning(f"重命名尝试 {attempt + 1} 失败: {rename_e}")
                    if attempt < 2:  # 不是最后一次尝试
                        time.sleep(1)
                        # 尝试修改新名称
                        old_workspace = directory.parent / f"simion_workspace_old_{timestamp}_{attempt + 1}"
            
            logger.error("所有删除方法都失败了")
            return False
            
        except Exception as e:
            logger.error(f"删除目录失败: {e}")
            return False
    
    def _make_directory_writable(self, directory):
        """递归修改目录权限为可写"""
        try:
            for root, dirs, files in os.walk(directory):
                # 修改目录权限
                os.chmod(root, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
                
                # 修改文件权限
                for file in files:
                    file_path = os.path.join(root, file)
                    try:
                        os.chmod(file_path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
                    except (OSError, FileNotFoundError):
                        pass
        except Exception as e:
            logger.warning(f"修改权限失败: {e}")
    
    def _start_background_cleanup(self, workspace_dir):
        """启动后台清理任务"""
        def background_cleanup():
            logger.info("启动后台清理任务...")
            
            # 等待一段时间让文件解锁
            for i in range(5):
                time.sleep(10)  # 等待10秒
                logger.info(f"后台清理尝试 {i + 1}/5...")
                
                try:
                    if not workspace_dir.exists():
                        logger.info("✅ 后台清理成功，工作空间目录已被删除")
                        return
                    
                    # 尝试删除
                    shutil.rmtree(workspace_dir, ignore_errors=True)
                    
                    if not workspace_dir.exists():
                        logger.info("✅ 后台清理成功，工作空间目录已被删除")
                        return
                        
                except Exception as e:
                    logger.warning(f"后台清理尝试 {i + 1} 失败: {e}")
            
            logger.warning("⚠️ 后台清理失败，工作空间目录可能仍存在")
            logger.warning(f"请手动删除: {workspace_dir}")
        
        # 在后台线程中运行清理任务
        import threading
        cleanup_thread = threading.Thread(target=background_cleanup, daemon=True)
        cleanup_thread.start()
        logger.info("后台清理任务已启动，将在后台尝试删除工作空间")
    
    def _get_directory_size(self, directory):
        """计算目录大小（字节）"""
        total_size = 0
        try:
            for dirpath, dirnames, filenames in os.walk(directory):
                for filename in filenames:
                    filepath = os.path.join(dirpath, filename)
                    try:
                        file_size = os.path.getsize(filepath)
                        total_size += file_size
                    except (OSError, FileNotFoundError):
                        pass
        except Exception as e:
            logger.error(f"计算目录大小失败: {e}")
        return total_size
    
    def _format_size(self, size_bytes):
        """格式化文件大小"""
        if size_bytes == 0:
            return "0 B"
        
        size_names = ["B", "KB", "MB", "GB", "TB"]
        i = 0
        while size_bytes >= 1024 and i < len(size_names) - 1:
            size_bytes /= 1024.0
            i += 1
        
        return f"{size_bytes:.1f} {size_names[i]}"


# 全局运行器实例
simion_runner = SimionRunner()

def run_simion_automation(config):
    """运行SIMION自动化的入口函数"""
    return simion_runner.run_automation(config)

def stop_simion_automation():
    """停止SIMION自动化"""
    simion_runner.stop_automation()

def get_simion_status():
    """获取SIMION运行状态"""
    return simion_runner.get_status()
